home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Shareware Grab Bag
/
Shareware Grab Bag.iso
/
007
/
a86b.arc
/
MACRO.DOC
< prev
next >
Wrap
Text File
|
1986-06-23
|
17KB
|
432 lines
---MACRO.DOC---
Macro Facility
----- --------
This assembler contains an easy-to-use, but very powerful macro facility.
The facility subsumes the capabilities of most assemblers, including operand
concatenation, indefinite repeat (often called IRP), and indefinite-repeat
character (IRPC). Unlike other assemblers, this assembler integrates these
functions into the main macro facility; so they can be invoked without clumsy
syntax, or strange characters in the macro-call operands.
Simple Macro Syntax
All macros must be defined before they are used. A macro definition consists
of the name of the macro, followed by the word MACRO, followed by the text of
the macro, followed by #EM, which marks the end of the macro.
Many assembly languages require a list of dummy operand-names to follow the
word MACRO. This assembler does not: the operands are denoted in the text
with the fixed names #1, #2, #3, ... up to a limit of #9, for each operand
in order. If there is anything following the word MACRO, it is considered
part of the macro text.
Examples:
; CLEAR sets the register-operand to zero.
CLEAR MACRO SUB #1,#1 #EM
CLEAR AX ; generates a SUB AX,AX instruction
CLEAR BX ; generates a SUB BX,BX instruction
; MOVM moves the second operand to the first operand. Both operands can be
; memory-variables.
MOVM MACRO
MOV AX,#2
MOV #1,AX
#EM
VAR1 DB ?
VAR2 DB ?
MOVM VAR1,VAR2 ; generates MOV AX,VAR2 followed by MOV VAR1,AX
Formatting in macro definitions and calls
The format of a macro definition is flexible. If the macro text consists of a
single instruction, the definition can be given in a single line, as in the
CLEAR macro given above. There is no particular advantage to doing this,
however: the assembler prunes all unnecessary spaces, blank lines, and
comments from the macro text before entering the text into the symbol table.
I recommend the more spread-out format of the MOVM macro, for program
readability.
All special macro-operators within a macro definition begin with a pound-sign
#. The letters following the pound-sign can be given in either upper-case or
lower-case. Pound-sign operators are recognized even within quoted strings. If
you wish the pound-sign to be treated literally, and not as the start of a
special macro-operator, you must give 2 consecutive pound signs: ##. For
example:
FOO MACRO
DB '##1'
DB '#1'
#em
FOO abc ; produces DB '#1' followed by DB 'abc'
The format of the macro call line is also flexible. A macro call consists of
the name of the macro, followed by the operands to be plugged into the macro.
The assembler prunes leading and trailing blanks from the operands of a
macro call. The operands to a macro call are always separated by commas.
Also, as in all assembler source lines, a semi-colon occurring outside of
a quoted-string is the start of a comment, ignored by the assembler. If you
want to include commas, blanks, or semi-colons in your operands, you must
enclose your operand in single-quotes.
Macro operand substitution
Some macro assemblers expect the operands to macro calls to follow the same
syntax as the operands to instructions. In those assemblers, the operands
are parsed, and reduced to numeric values before being plugged into the
macro definition text. This is called "passing by value". This assembler
does not pass by value, it passes by text. The only parsing of operands
done by the macro processor is to determine the start and the finish of the
operand text. That text is substituted, without regard for its contents,
for the "#n" that appears in the macro definition. The text is interpreted
by the assembler only after a complete line is expanded and as it is assembled.
If the first non-blank character after the macro name is a comma, then the
first operand is null: any occurrances of #1 in the macro text will be
deleted, and replaced with nothing. Likewise, any two consecutive commas
with no non-blanks between them will result in the corresponding null
operand. Also, out-of-range operands are null; for example, #3 is a null
operand if only two operands are provided in the call.
Null operands to macros are not in themselves illegal. They will produce
errors only if the resulting macro expansion is illegal.
The method of passing by text allows operand-text to be plugged anywhere
into a macro, even within symbol names. For example:
; KF_ENTRY creates an entry in the KFUNCS table, consistsing of a pointer
; to a KF_-action-routine. It also declares the corresponding CF_-symbol,
; which is the index within the table for that entry.
KF_ENTRY MACRO
CF_#1 EQU ($-KFUNCS)/2+080
DW KF_#1
#EM
KFUNCS:
KF_ENTRY UP
KF_ENTRY DOWN
; The above code is equivalent to:
;
; KFUNCS:
; DW KF_UP
; DW KF_DOWN
;
; CF_UP EQU 080
; CF_DOWN EQU 081
Quoted-string operands
As mentioned before, if you want to include blanks, commas, or semicolons in
your operands, you enclose the operand in single-quotes. In the vast majority
of cases in which these special characters need to be part of operands, the
user wants them to be quoted in the final, assembled line also. Therefore, the
quotes are passed in the operand. To override this, and strip the quotes from
the string, you precede the quoted string with a pound-sign. Examples:
DBW MACRO
DB #1
DW #2
#EM
DBW 'E', E_POINTER
DBW 'W', W_POINTER
; note that if quotes were not passed, the above lines would have to be
; DBW '''E''', E_POINTER; DBW '''W''', W_POINTER
GENERAL_PUSH MACRO
PUSH#1
#EM
GENERAL_PUSH F ; generates a PUSHF instruction
GENERAL_PUSH #' AX' ; generates a PUSH AX instruction
The fact that I could not come up with a more useful example than GENERAL_PUSH
is strong evidence that it is much better to pass the quotes as the default
action.
Looping by operands in macros
This macro facility contains two kinds of loops: you can loop once for each
operand in a range of operands; or you can loop once for each character within
an operand. The first kind of loop, the R-loop, is discussed in this section;
the second kind, the C-loop, is discussed later.
An R-loop is a stretch of macro-definition code that is repeated when the macro
is expanded. In addition to the fixed operands #1 through #9, you can specify
a variable operand, whose number changes each time through the loop. You give
the variable operand one of the 4 names #W, #X, #Y, or #Z.
An R-loop begins with #R, followed immediately by the letter W,X,Y, or Z naming
the variable, followed by the number of the first operand to be used, followed
by the number of the last operand to be used. After the #Rxnn is the text to
be repeated. The R-loop ends with #ER. For example:
STORE3 MACRO
MOV AX,#1
#RY24 ; "repeat for Y running from 2 through 4"
MOV #Y,AX
#ER
#EM
STORE3 VAR1,VAR2,VAR3,VAR4
; the above call produces the 4 instructions MOV AX,VAR1; MOV VAR2,AX;
; MOV VAR3,AX; MOV VAR4,AX.
The #L last operator and indefinite repeats
The macro facility recognizes the special operator #L, which is the last
operand in a macro call. #L can appear anywhere in macro text; but its big
power occurs in conjunction with R-loops, to yield an indefinite-repeat
facility.
A common example is as follows: you can take any macro that is designed
for one operand, and easily convert it into a macro that accepts any number
of operands. You do this by placing the command #RX1L, "repeat for X running
from 1 through L", at the start of the macro, and the command #ER at the end
just before the #EM. Finally, you replace all instances of #1 in the macro
with #X. We see how this works with the CLEAR macro:
CLEAR MACRO #RX1L
SUB #X,#X
#ER
#EM
CLEAR AX,BX ; genearates both SUB AX,AX and SUB BX,BX in one macro-call!
It is possible for R-loops to iterate zero times. In this case, the loop-text
is skipped completely. For example, CLEAR without any operands would produce no
expanded text.
Character-loops
We have seen the R-loop; now we discuss the other kind of loop in macros,
the character-loop, or C-loop. In the C-loop, the variable W,X,Y, or Z
does not represent an entire operand; it represents a character within an
operand.
You start a C-loop with #C, followed by one of the 4 letters W,X,Y, or Z,
followed by a single operand-specifier. Following the #Cxn is the text of
the C-loop. The C-loop ends with #EC. The macro will loop once for every
character in the operand. That single character will be substituted for each
instance of the indicated variable-operand. For example:
PUSHC MACRO #CW1
PUSH #WX
#EC#EM
PUSHC ABC ; generates the 3 instructions PUSH AX; PUSH BX; PUSH CX
If the C-operand is quoted in the macro call, the quotes ARE removed from
the operand before passing characters to the loop. It is not necessary
to precede the quoted string with a pound-sign in this case. If you do,
the pound-sign will be passed as the first character.
If the C-operand is a null operand (no characters in it), the loop-text
is skipped completely.
The "B"-before and "A"-after operators
So far, we have seen that you can specify operands in your macro in fourteen
different ways: 1,2,3,4,5,6,7,8,9,W,X,Y,Z,L. We now multiply these 14
possibilities, by introducing the "A" and "B" operators. You can precede any
of the 14 specifiers with "A" or "B", to get the adjacent operand after
or before the specified operand. For example, BL means the operand just
before the last operand; in other words, the second-to-the-last operand.
AZ means the operand just after the Z operand. You can even repeat, up to
a limit of 4 "B"s or 3 "A"s: BBL is the third-to-last operand; #AAA9 can be
used where you would want to (but cannot) use #12.
In the case of the variable operand to a C-loop, the "A" and "B" specifiers
denote the characters before or after the current looping-character. An
example of this is given in the next section.
Multiple-increments within loops
We have seen that you end an R-loop with a #ER, and you end a C-loop with
a #EC. We now present another way to end these loops; a way that lets you
specify a larger increment to the macro's loop-counter. You can end your
loops with one of the 4 additional commands #E1, #E2, #E3, or #E4.
For R-loops terminated by #ER, the variable-operand advances to the next
operand when the loop is made. If you end your R-loop with #E2, the variable-
operand advances 2 operands, not just one. For #E3, it advances 3 operands;
for #E4, 4 operands. The #E1 command is the same as #ER.
The most common usage of this feature is as follows: You will recall that we
generalized the CLEAR macro with an R-loop, so that it would take an
indefinite number of operands. Suppose we want to do the same thing with
the DBW macro. We would like DBW to take any number of operands, and
alternate DBs and DWs indefinitely on the operands. This is made possible
by creating an R-loop terminated by #E2:
DBW MACRO #RX1L
DB #X
DW #AX
#E2
#EM
DBW 'E',E_POINTER, 'W',W_POINTER ; two pairs on the same line!
The #E2 terminator means that we are looping on a pair of operands.
Note the crucial usage of the "A"-after operator to specify the second
operand of the operand-pair.
A special note applies to the DBW macro above: the assembler just happens to
accept a DW directive with no operands (it generates no object code, and
issues no error). This means that DBW will accept an odd number of operands
with no error, and do the expected thing (it alternates bytes and words, ending
with a byte).
You could likewise genralize a macro with 3 or 4 operands, to an indefinite
number of triples or quadruples; by ending the R-loop with #E3 or #E4. The
operands in each group would be specified by #X, #AX, #AAX, and, for #E4,
#AAAX.
For C-loops terminated by #E1 through #E4, the character-pointer is advanced
the specified number of characters. You use this in much the same way as for
R-loops, to create loops on pairs, triplets, and quadruplets of characters.
For example:
PUSHC2 MACRO #CZ1
PUSH #Z#AZ
#E2
#EM
PUSHC2 AXBXSIDI ; generates PUSH AX; PUSH BX; PUSH SI; PUSH DI
Negative R-loops
We now introduce another form of R-loop, called the Q-loop-- the negative
repeat-loop. This loop is the same as the R-loop, except that the operand
number decrements instead of increments; and the loop exits when the number
falls below the finish-number, not above it. The Q-loop is specified by
#Qxnn instead of #Rxnn, and #EQ instead of #ER. You can also use the
multiple-decrement forms #E1 #E2 #E3 or #E4 to terminate an Q-loop.
Example:
MOVN MACRO #QXL2 ; "negative-repeat X from L down to 2"
MOV #BX,#X
#EQ#EM
MOVN AX,BX,CX,DX ; generates the three instructions:
; MOV CX,DX
; MOV BX,CX
; MOV AX,BX
Note: the above functionality is already built into the MOV instruction of
the assembler. The macro shows how you would implement it if you did not
already have this facility.
Nesting of loops in macros
This macro facility allows nesting of loops within each other. Since we
provide the 4 identifiers W,X,Y,Z for the loop-operands, you can nest to
a level of 4 without restriction-- just use a different letter for each
nesting level. You can nest even deeper, subject to the restriction that
a letter W,X,Y,Z refers to the innermost containing loop that defines it.
Implied closing of loops
If you have a loop or loops ending when the macro ends, and if the iteration
count for those loops is 1, you may omit the #ER, #EC, or #EQ. The assembler
closes all open loops when it sees #EM, with no error.
For example, if you omit the #ER for the loop-version of the CLEAR macro,
it would make no difference-- the assembler automatically places an #ER
code into the macro definition for you.
Local labels in macros
Some assemblers have a LOCAL pseudo-op that is used in conjunction with
macros. Symbols declared LOCAL to a macro have unique (and bizarre)
symbol-names substituted for them each time the macro is called. This
solves the problem of duplicate label definitions when a macro is called
more than once.
In this assembler, the problem is solved more elegantly, by having a class
of generic local labels throughout assembly, not just in macros. Recall
that symbols consisting of a single letter, followed by one or more
decimal digits, can be redefined. You can use such labels in your macro
definitions.
I have recommended that local labels outside of macros be designated L1
through L9. Within macro definitions, I suggest that you use labels
M1 through M9. If you used an Ln-label within a macro, you would have
to make sure that you never call the macro within the range of definition
of another Ln-label with the same name. By using Mn-labels, you avoid
such potential conflicts.
The following example of a local label within a macro is taken from the
source of the macro-processor itself:
; "JPOUND label" checks to see if AL is a pound sign. If it is, it processes
; the pound-sign term, and jumps to label. Otherwise, it drops through
; to the following code.
JPOUND MACRO
CMP AL,'##' ; is the scanned character a pound-sign?
JNE >M1 ; skip if not
CALL MDEF_POUND ; process the pound sign
JMP #1 ; jump to the label provided
M1:
#EM
...
L3: ; loop here to consume empty lines and leading blanks
CALL SKIP_BLANKS ; skip over the leading blanks of a line
INC SI ; advance source pointer beyond the next non-blank
JPOUND L3 ; if pound-sign then process, and consume more blanks
CMP AL,0A ; were the blanks terminated by a linefeed?
JE L3 ; loop if yes, nothing on this line
L5: ; loop here after a line is seen to have contents
CMP AL,';' ; have we reached the start of a comment?
JE L1 ; jump if yes, to consume the comment
JPOUND >L6 ; if pound-sign then process it, and get another char
...
L6:
LODSB ; fetch the next definition-char from the source
CMP AL,' ' ; is it blank?
JA L5 ; loop if not, to process it
...
Debugging macro expansions
There is a tool called EXMAC which will help you troubleshoot program lines
that call macros. If you are not sure about what code is being generated by
your macro calls, EXMAC will tell you. See the file EXMAC.DOC for details.